home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Cream of the Crop 3
/
Cream of the Crop 3.iso
/
comm
/
wnos5src.zip
/
N8250.C
< prev
next >
Wrap
Text File
|
1993-11-17
|
16KB
|
654 lines
/* OS- and machine-dependent stuff for the 8250 asynch chip on a IBM-PC
* Copyright 1991 Phil Karn, KA9Q
*
* 16550A support plus some statistics added mah@hpuviea.at 15/7/89
*
* CTS hardware flow control from dkstevens@ucdavis,
* additional stats from delaroca@oac.ucla.edu added by karn 4/17/90
* Feb '91 RLSD line control reorganized by Bill_Simpson@um.cc.umich.edu
* Sep '91 All control signals reorganized by Bill Simpson
* Apr '92 Control signals redone again by Phil Karn
*/
#include <stdio.h>
#include <dos.h>
#include "global.h"
#include "config.h"
#ifdef ASY
#include "mbuf.h"
#include "proc.h"
#include "iface.h"
#include "asy.h"
#include "pc.h"
#include "slip.h"
#include "n8250.h"
#include "devparam.h"
#ifdef NRS
#include "nrs.h"
#endif
static void asy_tx __ARGS((int dev,void *p1,void *p2));
struct asy Asy[ASY_MAX] = {0,0,0,0,0,0};
/* ASY interrupt handlers */
static INTERRUPT (*Handle[ASY_MAX])() =
{asy0vec,asy1vec,asy2vec,asy3vec,asy4vec,asy5vec};
/* Initialize asynch port "dev" */
int
asy_init(int dev,struct iface *iface,char *arg1,char *arg2,unsigned bufsize,int trigchar,long speed,int cts,int rlsd)
{
struct fifo *fp;
unsigned base;
struct asy *ap = &Asy[dev];
if(bufsize) {
ap->iface = iface;
ap->addr = htoi(arg1);
ap->vec = htoi(arg2);
ap->trigchar = trigchar;
ap->chain = (strchr(arg2,'c') != NULLCHAR);
/* Set up receiver FIFO */
fp = &ap->fifo;
fp->buf = mxallocw(bufsize);
fp->bufsize = bufsize;
fp->wp = fp->rp = fp->buf;
fp->cnt = fp->hiwat = fp->overrun = 0;
}
base = ap->addr;
/* Purge the receive data buffer */
inportb(base+RBR);
DISABLE();
/* Save original interrupt vector, mask state, control bits */
ap->save.vec = getirq(ap->vec);
ap->save.mask = getmask(ap->vec);
ap->save.lcr = inportb(base+LCR);
ap->save.ier = inportb(base+IER);
ap->save.mcr = inportb(base+MCR);
ap->msr = ap->save.msr = inportb(base+MSR);
/* save speed bytes */
setbit(base+LCR,LCR_DLAB);
ap->save.divl = inportb(base+DLL);
ap->save.divh = inportb(base+DLM);
clrbit(base+LCR,LCR_DLAB);
/* save modem control flags */
ap->cts = cts;
ap->rlsd = rlsd;
/* Set interrupt vector to SIO handler */
setirq(ap->vec,Handle[dev]);
/* Set line control register: 8 bits, no parity */
outportb(base + LCR,uchar(LCR_8BITS));
/* determine if 16550A, turn on FIFO mode and clear RX and TX FIFOs */
outportb(base + FCR,uchar(FIFO_ENABLE));
/* According to National ap note AN-493, the FIFO in the 16550 chip
* is broken and must not be used. To determine if this is a 16550A
* (which has a good FIFO implementation) check that both bits 7
* and 6 of the IIR are 1 after setting the fifo enable bit. If
* not, don't try to use the FIFO.
*/
if((inportb(base+IIR) & IIR_FIFO_ENABLED) == IIR_FIFO_ENABLED) {
ap->is_16550a = TRUE;
outportb(base + FCR,uchar(FIFO_SETUP));
} else {
/* Chip is not a 16550A. In case it's a 16550 (which has a
* broken FIFO), turn off the FIFO bit.
*/
outportb(base+FCR,0);
ap->is_16550a = FALSE;
}
/* Turn on receive interrupts and optionally modem interrupts;
* leave transmit interrupts off until we actually send data.
*/
if(ap->rlsd || ap->cts) {
outportb(base+IER,IER_MS|IER_DAV);
} else {
outportb(base+IER,IER_DAV);
}
/* Turn on 8250 master interrupt enable (connected to OUT2) */
setbit(base+MCR,MCR_OUT2);
/* Enable interrupt */
maskon(ap->vec);
RESTORE();
asy_speed(dev,speed);
if(bufsize) {
char *ifn = if_name(iface," tx");
iface->proc1 = newproc(ifn,256,asy_tx,dev,iface,NULL,0);
xfree(ifn);
}
return 0;
}
int
asy_stop(struct iface *iface,int tmp)
{
struct asy *ap = &Asy[iface->dev];
unsigned base = ap->addr;
if(iface == NULLIF) {
return -1;
}
/*-------------------------------------------------------------------*
* A few notes to the delay below: *
* It sometimes happ%ned, that NOS simply hangs when an exit is issued*
* and more than one remote session is active. *
* the reset_all() in between doexit() resets all the remote connec- *
* tions causing a RST packet being sent to all those remote instances*
* During this work, NOS tries to detach the interfaces and in case *
* a 16650A is involved, the hardware FIFO is reset and POOF! *
* The delay gives enough time to send the packets (I hope) DK5DC *
*--------------------------------------------------------------------*/
pause(500); /* let the chip send all data */
if(!tmp) {
ap->iface = NULLIF;
/* Release slip or nrs block */
#ifdef SLIP
if(Slip[iface->xdev].iface == iface)
Slip[iface->xdev].iface = NULLIF;
#endif
#ifdef NRS
if(Nrs[iface->xdev].iface == iface)
Nrs[iface->xdev].iface = NULLIF;
#endif
}
stop_timer(&ap->idle);
/* Purge the receive data buffer */
inportb(base + RBR);
if(ap->is_16550a) {
/* Purge hardware FIFOs and disable. Apparently some
* other comm programs can't handle 16550s already in
* FIFO mode; they expect 16450 compatibility mode.
*/
outportb(base + FCR,uchar(FIFO_SETUP));
outportb(base + FCR,0);
}
DISABLE();
/* Restore original interrupt vector and 8259 mask state */
setirq(ap->vec,ap->save.vec);
if(ap->save.mask) {
maskon(ap->vec);
} else {
maskoff(ap->vec);
}
/* Restore speed regs */
setbit(base+LCR,LCR_DLAB);
outportb(base+DLL,ap->save.divl); /* Low byte */
outportb(base+DLM,ap->save.divh); /* Hi byte */
clrbit(base+LCR,LCR_DLAB);
/* Restore control regs */
outportb(base+LCR,ap->save.lcr);
outportb(base+IER,ap->save.ier);
outportb(base+MCR,ap->save.mcr);
RESTORE();
if(!tmp)
xfree(ap->fifo.buf);
return 0;
}
/* Set asynch line speed */
int
asy_speed(int dev,long speed)
{
struct asy *ap = &Asy[dev];
unsigned base = ap->addr;
int32 divisor = BAUDCLK / speed;
if(speed <= 0 || dev >= ASY_MAX || ap->iface == NULLIF) {
return -1;
}
ap->speed = speed;
DISABLE();
/* Purge the receive data buffer */
inportb(base+RBR);
if(ap->is_16550a) { /* clear tx+rx fifos */
outportb(base + FCR,uchar(FIFO_SETUP));
}
/* Turn on divisor latch access bit */
setbit(base+LCR,LCR_DLAB);
/* Load the two bytes of the register */
outportb(base+DLL,uchar(divisor & 0xff)); /* Low byte */
outportb(base+DLM,uchar((divisor >> 8) & 0xff)); /* Hi byte */
/* Turn off divisor latch access bit */
clrbit(base+LCR,LCR_DLAB);
RESTORE();
return 0;
}
/* Asynchronous line I/O control */
int32
asy_ioctl(struct iface *ifp,int cmd,int set,int32 val)
{
struct asy *ap = &Asy[ifp->dev];
int16 base = ap->addr;
base += MCR;
switch(cmd){
case PARAM_SPEED:
if(set) {
asy_speed(ifp->dev,val);
}
return ap->speed;
case PARAM_DTR:
if(set) {
writebit(base,MCR_DTR,(int)val);
}
return (inportb(base) & MCR_DTR) ? TRUE : FALSE;
case PARAM_RTS:
if(set) {
writebit(base,MCR_RTS,(int)val);
}
return (inportb(base) & MCR_RTS) ? TRUE : FALSE;
case PARAM_DOWN:
clrbit(base,MCR_RTS);
clrbit(base,MCR_DTR);
return FALSE;
case PARAM_UP:
setbit(base,MCR_RTS);
setbit(base,MCR_DTR);
return TRUE;
}
return -1;
}
/* Blocking read from asynch line
* Returns count of characters read
*/
int
get_asy(int dev)
{
struct fifo *fp = &Asy[dev].fifo;
int c;
DISABLE();
while(fp->cnt == 0) {
if(pwait(fp) != 0) {
RESTORE();
return -1;
}
}
fp->cnt--;
RESTORE();
c = *fp->rp++;
if(fp->rp >= &fp->buf[fp->bufsize]) {
fp->rp = fp->buf;
}
return c;
}
/* Process 8250 receiver interrupts */
static void near
asyrxint(struct asy *asyp)
{
unsigned char c, lsr;
int cnt = 0, trigseen = FALSE;
unsigned base = asyp->addr;
struct fifo *fp = &asyp->fifo;
asyp->rxints++;
for(;;){
if((lsr = inportb(base + LSR)) & LSR_OE)
asyp->overrun++;
if(lsr & LSR_DR) {
asyp->rxchar++;
if((c = inportb(base + RBR)) == asyp->trigchar) {
trigseen = TRUE;
}
/* If buffer is full, we have no choice but
* to drop the character
*/
if(fp->cnt != fp->bufsize) {
*fp->wp++ = c;
if(fp->wp >= &fp->buf[fp->bufsize]) {
/* Wrap around */
fp->wp = fp->buf;
}
if(++fp->cnt > fp->hiwat) {
fp->hiwat = fp->cnt;
}
cnt++;
} else {
fp->overrun++;
}
} else {
break;
}
}
if(cnt > asyp->rxhiwat) {
asyp->rxhiwat = cnt;
}
if(trigseen) {
psignal(fp,1);
}
return;
}
/* Handle 8250 transmitter interrupts */
static void near
asytxint(struct asy *asyp)
{
unsigned base = asyp->addr;
struct dma *dp = &asyp->dma;
asyp->txints++;
if(!dp->busy || (asyp->cts && !(asyp->msr & MSR_CTS))){
/* These events "shouldn't happen". Either the
* transmitter is idle, in which case the transmit
* interrupts should have been disabled, or flow control
* is enabled but CTS is low, and interrupts should also
* have been disabled.
*/
clrbit(base+IER,IER_TxE);
return; /* Nothing to send */
}
if(!(inportb(base+LSR) & LSR_THRE)) {
/* Not really ready */
return;
}
/* If it's a 16550A, load up to 16 chars into the tx hw fifo
* at once. With an 8250, it can be one char at most.
*/
if(asyp->is_16550a) {
int count = dp->cnt;
if(count > OUTPUT_FIFO_SIZE) {
count = OUTPUT_FIFO_SIZE;
}
/* 16550A: LSR_THRE will drop after the first char loaded
* so we can't look at this bit to determine if the hw fifo is
* full. There seems to be no way to determine if the tx fifo
* is full (any clues?). So we should never get here while the
* fifo isn't empty yet.
*/
asyp->txchar += count;
dp->cnt -= count;
#ifdef XXX /* This is apparently too fast for some chips */
dp->data = outbuf(base+THR,dp->data,count);
#else
while(count-- != 0) {
outportb(base+THR,*dp->data++);
}
#endif
} else do { /* 8250 */
asyp->txchar++;
outportb(base+THR,*dp->data++);
} while(--dp->cnt != 0 && inportb(base+LSR) & LSR_THRE);
if(dp->cnt == 0) {
dp->busy = 0;
/* Disable transmit interrupts */
clrbit(base+IER,IER_TxE);
psignal(asyp,1);
}
}
/* Handle 8250 modem status change
* If the signals are unchanging, we ignore them.
* If they change, we use them to condition the line.
*/
static void near
asymsint(struct asy *ap)
{
unsigned base = ap->addr;
ap->msr = inportb(base+MSR);
ap->msint_count++;
if(ap->cts && (ap->msr & MSR_DCTS)) {
/* CTS has changed and we care */
if(ap->msr & MSR_CTS) {
/* CTS went up */
if(ap->dma.busy) {
/* enable transmit interrupts and kick */
setbit(base+IER,IER_TxE);
asytxint(ap);
}
} else {
/* CTS now dropped, disable Transmit interrupts */
clrbit(base+IER,IER_TxE);
}
}
if(ap->rlsd && (ap->msr & MSR_DRLSD)) {
/* RLSD just changed and we care, signal it */
psignal(&(ap->rlsd),1);
/* Keep count */
if(ap->msr & MSR_RLSD) {
ap->answers++;
} else {
ap->remdrops++;
}
}
if(ap->msr & (MSR_TERI | MSR_RI)) {
asy_ioctl(ap->iface,PARAM_UP,TRUE,0);
}
}
/* Interrupt handler for 8250 asynch chip (called from asyvec.asm) */
INTERRUPT (far *(asyint)(int dev))()
{
char iir;
struct asy *asyp = &Asy[dev];
while(((iir = inportb(asyp->addr+IIR)) & IIR_IP) == 0){
switch(iir & IIR_ID_MASK){
case IIR_RDA: /* Receiver interrupt */
asyrxint(asyp);
break;
case IIR_THRE: /* Transmit interrupt */
asytxint(asyp);
break;
case IIR_MSTAT: /* Modem status change */
asymsint(asyp);
break;
}
/* should happen at end of a single packet */
if(iir & IIR_FIFO_TIMEOUT) {
asyp->fifotimeouts++;
}
}
return asyp->chain ? asyp->save.vec : NULL;
}
/* Poll the asynch input queues; called on every clock tick.
* This helps limit the interrupt ring buffer occupancy when long
* packets are being received.
*/
void
asytimer(void)
{
struct asy *asyp;
for(asyp = Asy; asyp < &Asy[ASY_MAX]; asyp++) {
if(asyp->fifo.cnt != 0) {
psignal(&asyp->fifo,1);
}
if(asyp->dma.busy
&& (inportb(asyp->addr+LSR) & LSR_THRE)
&& (!asyp->cts || (asyp->msr & MSR_CTS))) {
asyp->txto++;
DISABLE();
asytxint(asyp);
RESTORE();
}
}
}
int
doasystat(int argc,char *argv[],void *p)
{
struct asy *asyp;
for(asyp = Asy; asyp < &Asy[ASY_MAX]; asyp++) {
int mcr = inportb(asyp->addr + MCR);
if(asyp->iface == NULLIF) {
continue;
}
tprintf("%s: bps %lu%s%s",
asyp->iface->name,
asyp->speed,
asyp->cts ? " [CTS flow control]" : "",
asyp->rlsd ? " [RLSD line control]" : "");
if(asyp->trigchar != -1) {
tprintf(" [trigger 0x%02x]",asyp->trigchar);
}
if(asyp->is_16550a) {
tprintf("\n NS16550A: FifoTO %lu TrigLev 0x%02x",
asyp->fifotimeouts,
FIFO_TRIGGER_LEVEL);
}
tprintf("\n MC: int %lu DTR %s RTS %s CTS %s DSR %s RI %s CD %s\n",
asyp->msint_count,
(mcr & MCR_DTR) ? "On" : "Off",
(mcr & MCR_RTS) ? "On" : "Off",
(asyp->msr & MSR_CTS) ? "On" : "Off",
(asyp->msr & MSR_DSR) ? "On" : "Off",
(asyp->msr & MSR_RI) ? "On" : "Off",
(asyp->msr & MSR_RLSD) ? "On" : "Off");
tprintf(" RX: int %lu chars %lu hwovrn %lu hwhiwat %lu swovrn %lu swhiwat %u\n",
asyp->rxints,
asyp->rxchar,
asyp->overrun,
asyp->rxhiwat,
asyp->fifo.overrun,
asyp->fifo.hiwat);
tprintf(" TX: int %lu chars %lu THRE TO %lu%s\n",
asyp->txints,
asyp->txchar,
asyp->txto,
asyp->dma.busy ? " BUSY" : "");
asyp->rxhiwat = 0;
asyp->fifo.hiwat = 0;
}
return 0;
}
/* Send a message on the specified serial line */
int
asy_send(int dev,struct mbuf *bp)
{
int backoff = -1;
struct asy *asyp = &Asy[dev];
if(dev < 0 || dev >= ASY_MAX) {
free_p(bp);
return -1;
}
stop_timer(&asyp->idle);
while(asyp->rlsd
&& (asyp->msr & MSR_RLSD) == 0
&& asyp->actfile != NULLCHAR) {
/* Line down, we need to redial
* But if it's failing, perform random binary exponential backoff
*/
if(backoff++ >= 0) {
int pw;
/*
alarm(1000L * random(30L << backoff));
*/
alarm(10000L);
pw = (int)pwait(&asyp->rlsd);
alarm(0L);
if(pw == 0) {
/* CD changed, maybe we got called */
continue;
}
}
asyp->originates++;
redial(asyp,1);
}
enqueue(&asyp->sndq,bp);
start_timer(&asyp->idle); /* Restart idle timeout */
return 0;
}
/* Serial transmit process, common to all protocols */
static void
asy_tx(int dev,void *p1,void *p2)
{
struct mbuf *bp = NULLBUF;
struct asy *asyp = &Asy[dev];
struct dma *dp = &asyp->dma;
unsigned base = asyp->addr + IER;
for( ; ;) {
/* Fetch a buffer for transmission */
while(asyp->sndq == NULLBUF) {
pwait(&asyp->sndq);
}
for(bp = dequeue(&asyp->sndq); bp; bp = free_mbuf(bp)) {
/* Start the transmitter */
dp->data = bp->data;
dp->cnt = bp->cnt;
dp->busy = 1;
/* If CTS flow control is disabled or CTS is true,
* enable transmit interrupts here so we'll take an immediate
* interrupt to get things going. Otherwise let the
* modem control interrupt enable transmit interrupts
* when CTS comes up. If we do turn on TxE,
* "kick start" the transmitter interrupt routine, in case just
* setting the interrupt enable bit doesn't cause an interrupt
*/
if(!asyp->cts || (asyp->msr & MSR_CTS)){
setbit(base,IER_TxE);
asytxint(asyp);
}
/* Wait for completion */
DISABLE();
while(dp->busy) {
pwait(asyp);
}
RESTORE();
}
}
}
#endif /* ASY */